package com._2048;

import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.event.KeyEvent;
import java.util.Random;

// 游戏
class Game {
    // 回调
    public interface Callback {
        void onGameOver(boolean isWon); // 游戏结束事件
    }
    
    // 状态
    public enum Status {
        END,    // 已结束
        PLAYING // 游戏中
    }

    private final Callback mCallback;   // 回调
    private final Graphics mDrawer;     // 绘图者
    private final Component mGameBoard; // 游戏区域
    private int[][] mNumbers;           // 数字矩阵
    private final Random mRandom;       // 随机数生成器
    private Status mStatus;             // 状态

    /**
     * 构造方法。
     * @param callback  回调
     * @param drawer    绘图者
     * @param gameBoard 游戏区域
     */
    public Game(Callback callback, Graphics drawer, Component gameBoard) {
        // 初始化成员
        mCallback = callback;
        mDrawer = drawer;
        final Font font = new Font(Const.GAME_BOARD_FONT_NAME, Font.PLAIN, Const.GAME_BOARD_FONT_SIZE);
        mDrawer.setFont(font);
        mGameBoard = gameBoard;
        mRandom = new Random();
        mStatus = Status.END;

        // 重绘游戏区域
        this.drawGameBoard();
    }

    /**
     * 移动之后要做的事。
     * @param changed   指示移动之后，每个格上的数字是否改变。任意一格有改变则为 true，否则为 false
     */
    private void afterMove(boolean changed) {
        // 重绘游戏区域
        this.drawGameBoard();

        // 画所有数字
        this.drawAllNumbers();

        // 如果胜利或失败，则游戏结束
        if (this.isWon()) {
            this.gameOver(true);
            return;
        }
        if (this.isLost()) {
            this.gameOver(false);
            return;
        }

        // 创建下一个数
        if (changed) {
            this.createRandomNumber();
        }
    }

    /**
     * 在所有格中，随机找到未被使用的一个，将格上的数字设为2或4。
     */
    private void createRandomNumber() {
        int row = mRandom.nextInt(Const.GAME_BOARD_GRID_COUNT_V);
        int line = mRandom.nextInt(Const.GAME_BOARD_GRID_COUNT_H);

        while (mNumbers[row][line] != 0) {
            row = mRandom.nextInt(Const.GAME_BOARD_GRID_COUNT_V);
            line = mRandom.nextInt(Const.GAME_BOARD_GRID_COUNT_H);
        }

        mNumbers[row][line] = mRandom.nextInt(8) <= 3 ? 2: 4;
        this.drawOneNumber(line, row, mNumbers[row][line]);
        mGameBoard.repaint();
    }
    
    /**
     * 画所有数字。
     */
    private void drawAllNumbers() {
        for (int i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            for (int j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                if (mNumbers[i][j] != 0) {
                    this.drawOneNumber(j, i, mNumbers[i][j]);
                }
            }
        }

        mGameBoard.repaint();
    }
    
    /**
     * 画游戏区域。
     */
    private void drawGameBoard() {
        // 画边框
        mDrawer.setColor(Color.BLACK);
        mDrawer.drawRect(0, 0, Const.GAME_BOARD_WIDTH - 1, Const.GAME_BOARD_HEIGHT - 1);

        // 画背景
        mDrawer.setColor(Const.GAME_BOARD_COLOR);
        mDrawer.fillRect(1, 1, Const.GAME_BOARD_WIDTH - 2, Const.GAME_BOARD_HEIGHT - 2);

        // 画竖线
        mDrawer.setColor(Color.BLACK);
        int i;
        for (i = 1; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            mDrawer.drawLine(Const.GRID_SIZE * i, 0, Const.GRID_SIZE * i, Const.GAME_BOARD_HEIGHT);
        }

        // 画横线
        for (i = 1; i < Const.GAME_BOARD_GRID_COUNT_H; i++) {
            mDrawer.drawLine(0, Const.GRID_SIZE * i, Const.GAME_BOARD_WIDTH, Const.GRID_SIZE * i);
        }

        // 通知游戏区域重绘
        mGameBoard.repaint();
    }
    
    /**
     * 画单个数字。
     * @param line      列
     * @param row       行
     * @param number    要画的数字
     */
    private void drawOneNumber(int line, int row, int number) {
        // 计算位置
        final String text = String.valueOf(number);
        final int x = line * Const.GRID_SIZE + (Const.GRID_SIZE - mDrawer.getFontMetrics().stringWidth(text)) / 2;
        final int y = row * Const.GRID_SIZE + (Const.GRID_SIZE + Const.GAME_BOARD_FONT_SIZE) / 2 - Const.GAME_BOARD_FONT_SIZE / 8;

        // 画数字
        mDrawer.drawString(text, x, y);
    }
    
    /**
     * 游戏结束。
     * @param isWon 胜利则为 true，否则为 false
     */
    private void gameOver(boolean isWon) {
        // 将状态设置为已结束
        mStatus = Status.END;

        // 发送通知
        mCallback.onGameOver(isWon);
    }
    
    /**
     * 判断是否失败。
     * @return 失败则返回 true，否则返回 false
     */
    private boolean isLost() {
        int i, j;   // 循环变量

        // 横向检查所有数字
        for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H - 1; j++) {
                if (mNumbers[i][j] == 0 || mNumbers[i][j] == mNumbers[i][j + 1]) {
                    return false;
                }
            }
        }

        // 纵向检查所有数字
        for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
            for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V - 1; i++) {
                if (mNumbers[i][j] == mNumbers[i + 1][j]) {
                    return false;
                }
            }
        }

        // 判断最右边一列是否有零
        for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            if (mNumbers[i][3] == 0) {
                return false;
            }
        }

        return true;
    }
    
    /**
     * 判断是否胜利。
     * @return 胜利则返回 true，否则返回 false
     */
    private boolean isWon() {
        // 检查所有数字
        for (int i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            for (int j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                if (mNumbers[i][j] == Const.WIN_NUMBER) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 下移。
     */
    private void moveDown() {
        int i, j, k;                // 循环变量
        int ignore = -1;            // 忽略的格
        boolean changed = false;    // 是否相加、移动

        // 从左到右遍历每一列
        for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
            // 每列从上到下遍历每一格，判断是否有相同数，有则相加
            for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || i == ignore) {
                    continue;
                }

                // 不为零则从该数下方逐个检查
                for (k = i + 1; k < Const.GAME_BOARD_GRID_COUNT_V; k++) {
                    // 遇到零跳过
                    if (mNumbers[k][j] == 0) {
                        continue;
                    }

                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[k][j] != mNumbers[i][j]) {
                        break;
                    }

                    // 两数相等则相加
                    mNumbers[k][j] += mNumbers[i][j];
                    mNumbers[i][j] = 0;
                    ignore = k;
                    changed = true;
                }
            }

            // 所有数字向下移动
            for (i = Const.GAME_BOARD_GRID_COUNT_V - 2; i >= 0; i--) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue;
                }
                
                // 不为零则向下移动
                for (k = i + 1; k < Const.GAME_BOARD_GRID_COUNT_V; k++) {
                    if (mNumbers[k][j] == 0) {
                        mNumbers[k][j] = mNumbers[k - 1][j];
                        mNumbers[k - 1][j] = 0;
                        changed = true;
                    }
                }
            }
        }

        this.afterMove(changed);
    }

    /**
     * 左移。
     */
    private void moveLeft() {
        int i, j, k;                // 循环变量
        int ignore = -1;            // 忽略的格
        boolean changed = false;    // 是否相加、移动

        // 从上到下遍历每一行
        for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            // 每行从右到左遍历每一格，判断是否有相同数，有则相加
            for (j = Const.GAME_BOARD_GRID_COUNT_H - 1; j > 0; j--) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || j == ignore) {
                    continue;
                }
                
                // 不为零则从该数左面逐个检查
                for (k = j - 1; k >= 0; k--) {
                    // 遇到零跳过
                    if (mNumbers[i][k] == 0) {
                        continue;
                    }
                    
                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[i][k] != mNumbers[i][j]) {
                        break;
                    }
                    
                    // 两数相等则相加
                    mNumbers[i][k] += mNumbers[i][j];
                    mNumbers[i][j] = 0;
                    ignore = k;
                    changed = true;
                }
            }

            // 所有数字向左移动
            for (j = 1; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue;
                }
                
                // 不为零则向左移动
                for (k = j - 1; k >= 0; k--) {
                    if (mNumbers[i][k] == 0) {
                        mNumbers[i][k] = mNumbers[i][k + 1];
                        mNumbers[i][k + 1] = 0;
                        changed = true;
                    }
                }
            }
        }

        this.afterMove(changed);
    }

    /**
     * 右移。
     */
    private void moveRight() {
        int i, j, k;                // 循环变量
        int ignore = -1;            // 忽略的格
        boolean changed = false;    // 是否相加、移动

        // 从上到下遍历每一行
        for (i = 0; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
            // 每行从左到右遍历每一格，判断是否有相同数，有则相加
            for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H - 1; j++) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || j == ignore) {
                    continue;
                }
                
                // 不为零则从该数右面逐个检查
                for (k = j + 1; k < Const.GAME_BOARD_GRID_COUNT_H; k++) {
                    // 遇到零跳过
                    if (mNumbers[i][k] == 0) {
                        continue;
                    }
                    
                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[i][k] != mNumbers[i][j]) {
                        break;
                    }
                    
                    // 两数相等则相加
                    mNumbers[i][k] += mNumbers[i][j];
                    mNumbers[i][j] = 0;
                    ignore = k;
                    changed = true;
                }
            }

            // 所有数字向右移动
            for (j = Const.GAME_BOARD_GRID_COUNT_H - 2; j >= 0; j--) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue;
                }
                
                // 不为零则向右移动
                for (k = j + 1; k < Const.GAME_BOARD_GRID_COUNT_H; k++) {
                    if (mNumbers[i][k] == 0) {
                        mNumbers[i][k] = mNumbers[i][k - 1];
                        mNumbers[i][k - 1] = 0;
                        changed = true;
                    }
                }
            }
        }

        this.afterMove(changed);
    }
    
    /**
     * 上移。
     */
    private void moveUp() {
        int i, j, k;                // 循环变量
        int ignore = -1;            // 忽略的格
        boolean changed = false;    // 是否相加、移动

        // 从左到右遍历每一列
        for (j = 0; j < Const.GAME_BOARD_GRID_COUNT_H; j++) {
            // 每列从下到上遍历每一格，判断是否有相同数，有则相加
            for (i = Const.GAME_BOARD_GRID_COUNT_V - 1; i > 0; i--) {
                // 如果该数为零或之前被加过，则什么也不做
                if (mNumbers[i][j] == 0 || i == ignore) {
                    continue;
                }
                
                // 不为零则从该数上方逐个检查
                for (k = i - 1; k >= 0; k--) {
                    // 遇到零跳过
                    if (mNumbers[k][j] == 0) {
                        continue;
                    }
                    
                    // 如果两数不相等，则结束本次循环
                    if (mNumbers[k][j] != mNumbers[i][j]) {
                        break;
                    }
                    
                    // 两数相等则相加
                    mNumbers[k][j] += mNumbers[i][j];
                    mNumbers[i][j] = 0;
                    ignore = k;
                    changed = true;
                }
            }

            // 所有数字向上移动
            for (i = 1; i < Const.GAME_BOARD_GRID_COUNT_V; i++) {
                // 如果该数为零（没有数），则什么也不做
                if (mNumbers[i][j] == 0) {
                    continue;
                }
                
                // 不为零则向上移动
                for (k = i - 1; k >= 0; k--) {
                    if (mNumbers[k][j] == 0) {
                        mNumbers[k][j] = mNumbers[k + 1][j];
                        mNumbers[k + 1][j] = 0;
                        changed = true;
                    }
                }
            }
        }

        this.afterMove(changed);
    }
    
    /**
     * 按键事件的响应方法。
     * @param key 按键
     */
    public void onKeyPressed(int key) {
        if (mStatus != Status.PLAYING) {
            return;
        }

        switch (key) {
            case KeyEvent.VK_DOWN:
                this.moveDown();
                return;
            case KeyEvent.VK_LEFT:
                this.moveLeft();
                return;
            case KeyEvent.VK_RIGHT:
                this.moveRight();
                return;
            case KeyEvent.VK_UP:
                this.moveUp();
        }

    }
    
    /**
     * 游戏开始。
     */
    public void start() {
        // 数字矩阵清零
        mNumbers = new int[Const.GAME_BOARD_GRID_COUNT_V][Const.GAME_BOARD_GRID_COUNT_H];

        // 重绘游戏区域
        this.drawGameBoard();

        // 创建随机数字
        this.createRandomNumber();

        // 将状态设置为游戏中
        mStatus = Status.PLAYING;
    }    
}
